#include <string.h>
#include <stdlib.h>
#include <stdarg.h>

#include "xml.h"
#include "debug.h"
#include "strlib.h"

#define TEXTCONTENT_WITH_WHITESPACE	0
#ifdef DEBUG_LIB_XML
#define xml_debug2(x, ...) debug2(x, __VA_ARGS__)
#define xml_debug3(x, ...) debug3(x, __VA_ARGS__)
#else
#define xml_debug2(x, ...)
#define xml_debug3(x, ...)
#endif

static bool xml_node_destroy (PXMLNODE node);

static char* xml_node_get_name (PXMLNODE node);
static char* xml_node_get_text_content (PXMLNODE node);
static char* xml_node_get_attribute (PXMLNODE node, char *attribute);
static PXMLNODE xml_node_get_child_by_id (PXMLNODE node, char *id);
static PXMLNODE xml_node_get_child_by_attribute (PXMLNODE node, char *attrname, char *attrvalue);
static PXMLNODE xml_node_get_child_by_index (PXMLNODE node, int index);
static PXMLNODE xml_node_get_child_by_name (PXMLNODE node, char *name, int index);
static PXMLNODE xml_node_get_descent_by_name (PXMLNODE node, int count, ...);
static char* xml_node_get_child_text_content_by_name (PXMLNODE node, char *name, int index);
static size_t xml_node_get_children_by_attribute (PXMLNODE node, PXMLNODE parent, char *attrname, char *attrvalue);
static int xml_node_get_child_index (PXMLNODE node, PXMLNODE src);
static PXMLNODE xml_node_get_parent (PXMLNODE node);
static size_t xml_node_get_attribute_count (PXMLNODE node);
static size_t xml_node_get_child_count (PXMLNODE node);

static bool xml_node_has_child (PXMLNODE node, PXMLNODE child);

static bool xml_node_set_name (PXMLNODE node, char *name);
static bool xml_node_set_text_content (PXMLNODE node, char *text);
static bool xml_node_append_attribute (PXMLNODE node, char *key, char *value);
static PXMLNODE xml_node_append_child (PXMLNODE node, PXMLNODE child);

static bool xml_node_remove_attribute (PXMLNODE node, char *key);
static PXMLNODE xml_node_remove_child (PXMLNODE node, PXMLNODE child);
static PXMLNODE xml_node_remove_child_by_index (PXMLNODE node, int index);

static int xml_node_read_xml (PXMLNODE node, PREADER reader);
static bool xml_node_write_xml_local (PXMLNODE node, PWRITER writer, int level, bool space);
static bool xml_node_write_xml (PXMLNODE node, PWRITER writer);
static bool xml_node_write_xml_no_ws (PXMLNODE node, PWRITER writer);

static bool xml_node_bind (PXMLNODE node, PXMLBIND bind);
static bool xml_node_bind_static (PXMLNODE node, PXMLBIND_STATIC bind, void *object);

static bool xml_node_destroy (PXMLNODE node)
{
	PLISTITERATOR iter;

	iter = node->childList->iterator (node->childList);
	
	xml_debug3("%s is destroyed", node->name);

	node->attributeMap->destroy (node->attributeMap);
	
	//destroy child node map
	while (iter->hasNext (iter))
	{
		PXMLNODE child = iter->next (iter);
		child->destroy (child);
	}
	
	if (node->textContent != NULL)
	{
		free (node->textContent);
	}

	if (node->name != NULL)
	{
		free (node->name);
	}

	//if parent has this, remove this from parent
	if (node->parent != NULL 
		&& node->parent->hasChild(node->parent, node) == true)
	{
		node->parent->removeChild (node->parent, node);
	}

	//destory xml node, iterator
	iter->destroy (iter);
	free (node);
	return true;
}


static char* xml_node_get_text_content (PXMLNODE node)
{
	return node->textContent;
}


static char* xml_node_get_attribute (PXMLNODE node, char *attribute)
{
	return node->attributeMap->get (node->attributeMap, attribute);
}

static PXMLNODE xml_node_get_child_by_id (PXMLNODE node, char *id)
{
	return xml_node_get_child_by_attribute (node, "id", id);
}

static PXMLNODE xml_node_get_child_by_attribute (PXMLNODE node, char *attrname, char *attrvalue)
{
	PLISTITERATOR iter = NULL;
	PXMLNODE      nodeChild = NULL;
	char    *value = NULL;

	iter = node->childList->iterator (node->childList);
	while (iter->hasNext (iter))
	{
		//search id attribute and compare argument.
		nodeChild = (PXMLNODE) iter->next (iter);
		value = nodeChild->getAttribute (nodeChild, attrname);

		//there is no id attribute in this xml node, search next.
		if (attrvalue == NULL)
			continue;

		//both are same, return that node.
		if (strcmp (value, attrvalue) == 0)
			goto success_return;
	}
	iter->destroy (iter);
	return NULL;

success_return:
	iter->destroy (iter);
	return nodeChild;
}

static PXMLNODE xml_node_get_child_by_index (PXMLNODE node, int index)
{
	return node->childList->elementAt (node->childList, index);
}

static size_t xml_node_get_children_by_attribute (PXMLNODE node, PXMLNODE nodeParent, char *attrname, char *attrvalue)
{
	PLISTITERATOR iter = node->childList->iterator (node->childList);
	PXMLNODE      nodeChild = NULL;
	char          *value = NULL;
	size_t        count = 0;

	while (iter->hasNext (iter))
	{
		//search id attribute and compare argument.
		nodeChild = (PXMLNODE) iter->next (iter);
		value = nodeChild->getAttribute (nodeChild, attrname);

		//there is no id attribute in this xml node, search next.
		if (attrvalue == NULL)
			continue;

		//both are same, collect that node.
		if (strcmp (value, attrvalue) == 0)
		{
			PXMLNODE nodeCopy = xml_node_copy (nodeChild);
			nodeParent->appendChild (nodeParent, nodeCopy);
			count++;
		}
	}
	iter->destroy (iter);
	return count;
}

static bool xml_node_set_text_content (PXMLNODE node, char* text)
{
	if (node->textContent != NULL)
	{
		free (node->textContent);
	}

	if (text == NULL)
	{
		xml_debug2 ("input is null");
		return false;
	}

	node->textContent = (char *) malloc (strlen (text) + 1);
	strncpy (node->textContent, text, strlen (text) + 1);

	if (strcmp (node->textContent, text) == 0)
	{
		return true;
	}
	else
	{	
		xml_debug2 ("text Content isn't setted");
		return false;
	}
}


static bool xml_node_append_attribute (PXMLNODE node, char *key, char *value)
{
	int start, end;

	start = node->attributeMap->size (node->attributeMap);
	
	//append item
	node->attributeMap->put (node->attributeMap, key, value);

	end = node->attributeMap->size (node->attributeMap);

	if (end - start != 1)
	{
		xml_debug2 ("cannot append attribute");
		return false;
	}
	else
	{
		return true;
	}
}

static PXMLNODE xml_node_append_child (PXMLNODE node, PXMLNODE child)
{
	//append child node
	node->childList->append (node->childList, child);
	child->parent = node;
	
	return child;
}

static PXMLNODE xml_node_get_parent (PXMLNODE node)
{
	return node->parent;
}


static bool xml_node_remove_attribute (PXMLNODE node, char *key)
{
	int start, end;
	start = node->attributeMap->size (node->attributeMap);
	node->attributeMap->remove (node->attributeMap, key);
	end = node->attributeMap->size (node->attributeMap);

	if (start - end != 1)
	{
		xml_debug2 ("cannot remove attribute");
		return false;
	}
	else
	{	
		return true;
	}
}


static PXMLNODE xml_node_remove_child (PXMLNODE node, PXMLNODE child)
{
	node->childList->remove (node->childList, child);
	child->parent = NULL;
	return child;

}

static char* xml_node_get_name (PXMLNODE node)
{
	return node->name;
}

static bool xml_node_set_name (PXMLNODE node, char *name)
{
	if (node->name != NULL)
	{
		free (node->name);
	}
	
	if (name == NULL)
	{
		xml_debug2 ("input is null");
		return false;
	}

	node->name = (char *) malloc (strlen (name) + 1);
	strncpy (node->name, name, strlen (name) + 1);

	if (strcmp (node->name, name) == 0)
	{
		return true;
	}
	else
	{
		xml_debug2 ("cannot set tag name");
		return false;
	}
}



static int xml_node_read_xml (PXMLNODE node, PREADER reader)
{
	char buffer[4096] = {0, };
	char *name = NULL;
	char *saveptr = NULL;
	char *textContent = NULL;
	int tagType = 1;
	//tagType
	// -1 : comment, define
	//  0 : tag close
	//  1 : tag open
	//  2 : self close

	int nameLength = 0;
	int tempLength = 0;
	PXMLNODE child = NULL;
	bool reparsing = true;

	//createo attribute
	PMAP attributeMap = strstrmap_create ();
	PMAPITERATOR iter = NULL;

	while(reparsing == true)
	{
		//reset variable
		memset(buffer, 0, 4096);
		name = NULL;
		saveptr = NULL;
		textContent = NULL;
		tagType = 1;

		//read tag "< ~ >"
		reader->readDelm (reader, buffer, ">");
		xml_debug3("Reading Msg : %s", buffer);
		if (strstr (buffer, "</") == NULL)
			string_trim (buffer);

		//if this node has text content
		if (buffer[0] != '<')
		{
			textContent = strtok_r (buffer, "<", &saveptr);
			if (strlen (textContent) != 1 || *textContent != ' ')
				string_trim (textContent);
			name = strtok_r (NULL, "<", &saveptr);

			xml_debug2 ("Text content : %s=[%s]", name, textContent);
		}
		else
		{
			name = strtok_r (buffer, "<", &saveptr);
			name = strtok_r (name, " \t", &saveptr);
		}
		string_trim (name);
		xml_debug2 ("Tag : %s", name);

		nameLength = strlen (name);
		tempLength = strlen (saveptr);
		if (nameLength == 0)
		{
			//empty
			node->destroy (node);
			return 0;
		}
		else if (name[0] == '/') 
		{
			//tag closeing
			//setting text content
			PXMLNODE parent = node->getParent (node);
			parent->setTextContent (parent, textContent);

			//delete </...>
			parent->removeChild (parent, node);
			node->destroy(node);
			return 0;
		}
		else if (*name == '!' && *(name + 1) == '-' && *(name + 2) == '-')
		{
			//comment...start with "<!--"
			xml_debug2 ("Comment : %s", name + 3);

			//delete <!--...
			PXMLNODE parent = node->getParent (node);
			if(parent != NULL)
			{
				//if not root node, continue parsing
				parent->removeChild (parent, node);
				node->destroy (node);
				return -1;
			}
		}
		else if (*name == '?')
		{
			//define
			xml_debug2 ("define : %s", name + 1);

			//delete <?...
			PXMLNODE parent = node->getParent (node);
			if(parent != NULL)
			{
				//if not root node, continue parsing
				parent->removeChild (parent, node);
				node->destroy (node);

				return -1;
			}
		}
		else if (*(name + nameLength - 1) == '/' || *(saveptr + tempLength - 1) == '/')
		{
			//if self closing
			string_replace(name, '/', 0);
			string_replace(saveptr, '/', 0);

			xml_debug2("self closing : %s", name);
			tagType = 2;
			reparsing = false;
		}
		else
		{
			//normal tag
			reparsing = false;
		}
	}

	//read attribute
	//if lenfth of saveptr is 0, no attribute
	if (strlen (saveptr) != 0)
	{
		char *token    = NULL;
		char *saveptr1 = NULL;
		char *str1     = NULL;
		char *key      = NULL;
		char *value    = NULL;

		int i;

		for (i = 0, str1 = saveptr ; ; i++, str1 = NULL)
		{
			token = strtok_r (str1, "\"", &saveptr1);
			if (token == NULL)
			{
				break;
			}

			//delete "=", " "
			if (strstr (token, "=") != NULL)
			{
				string_replace (token, '=', ' ');
				string_trim (token);
			}
			xml_debug2 ("%d : %s", i, token);
		
			//if i is even number, token = key
			if (i % 2 == 0) 
			{
				key = token;
			}
			//if i is odd number, token = value
			else
			{
				value = token;
				//add attribute to map
				attributeMap->put (attributeMap, key, value);
			}
		}
	}

	//setting node
	//1. setting node name
	node->setName (node, name);

	//2. setting node attribute
	iter = attributeMap->iterator (attributeMap);
	while (iter->hasNext (iter))
	{
		PAIR pair = iter->next (iter);
		node->appendAttribute (node, pair.key, pair.value);
	}
	

	//call recursive
	if (tagType != 2)
	{
		while (tagType != 0)
		{
			child = xml_node_create (node);
			tagType = child->readXML (child, reader);
		}
	}

	//delete attribute map, iterator
	attributeMap->destroy (attributeMap);
	iter->destroy (iter);

	if (tagType == 2)
	{
		return tagType;
	}
	return 1;
}

static size_t xml_node_get_attribute_count (PXMLNODE node)
{
	return node->attributeMap->size (node->attributeMap);

}


static size_t xml_node_get_child_count (PXMLNODE node)
{
	return node->childList->size (node->childList);
}


static bool xml_node_write_xml_local (PXMLNODE node, PWRITER writer, int level, bool space)
{
	int tab;
	char *textContent = NULL;
	char *attributeKey = NULL;
	char *attributeValue = NULL;
	PLISTITERATOR listIter = NULL;
	PMAPITERATOR mapIter = NULL;

	//put tabs
	if (space)
	{
		for (tab = 0; tab < level; tab++)
			writer->writeBuff (writer, "\t", 1);
	}
	//open tag
	xml_debug2 ("Element: %s", node->getName (node));
	writer->writef (writer, "<%s", node->getName (node));

	//get attribute 
	if (node->getAttributeCount (node) != 0)
	{
		mapIter = node->attributeMap->iterator (node->attributeMap);
		while (mapIter->hasNext (mapIter))
		{
			PAIR pair = mapIter->next (mapIter);
			attributeKey = pair.key;
			attributeValue = pair.value;

			//write attribute
			xml_debug2 ("Attribute: %s=\"%s\"", attributeKey, attributeValue);
			writer->writef (writer, " %s=\"%s\"", attributeKey, attributeValue);
		}
		mapIter->destroy (mapIter);
	}
	writer->writef (writer, ">");

	//get text content, (maybe not exist)
	textContent = node->getTextContent (node);
	if (textContent != NULL)
	{
#if TEXTCONTENT_WITH_WHITESPACE
		//put tabs
		if (space)
		{
			writer->writeBuff (writer, "\n", 1);
			for (tab = 0; tab <= level; tab++)
				writer->writeBuff (writer, "\t", 1);
		}
#endif

		//write text content
		xml_debug2 ("TextContent: %s", textContent);
		writer->writef (writer, "%s", textContent);

#if TEXTCONTENT_WITH_WHITESPACE
		//put new line character.
		if (space)
			writer->writeBuff (writer, "\n", 1);
#endif
	}

	//when needs space, checks if any child exists, put a new line char.
	if (space && node->childList->size (node->childList) != 0)
	{
		writer->writeBuff (writer, "\n", 1);
	}

	//call recursive
	listIter = node->childList->iterator (node->childList);
	while (listIter->hasNext (listIter))
	{
		PXMLNODE child = listIter->next (listIter);
		
		//write xml node recursively.
		xml_node_write_xml_local (child, writer, level + 1, space);
	}

#if TEXTCONTENT_WITH_WHITESPACE
	//when needs space, checks if any child exists, put tabs.
	if (space && (node->childList->size (node->childList) != 0
			 || node->getTextContent (node) != NULL))
	{
		for (tab = 0; tab < level; tab++)
			writer->writeBuff (writer, "\t", 1);
	}
#else
	//when needs space, checks if any child exists, put tabs.
	if (space && node->childList->size (node->childList) != 0)
	{
		for (tab = 0; tab < level; tab++)
			writer->writeBuff (writer, "\t", 1);
	}
#endif

	//write close tag
	xml_debug2 ("Element: %s closed.", node->getName (node));
	writer->writef (writer, "</%s>", node->getName (node));

	//put a new line char.
	if (space)
		writer->writeBuff (writer, "\n", 1);

	//ready to destory
	listIter->destroy (listIter);
	return true;
}

static bool xml_node_write_xml (PXMLNODE node, PWRITER writer)
{
	// write xml with white space.
	return xml_node_write_xml_local (node, writer, 0, true);
}

static bool xml_node_write_xml_no_ws (PXMLNODE node, PWRITER writer)
{
	// write xml without white space.
	return xml_node_write_xml_local (node, writer, 0, false);
}

static PXMLNODE xml_node_remove_child_by_index (PXMLNODE node, int index)
{
	PXMLNODE child = node->childList->removeAt (node->childList, index);
	child->parent = NULL;
	return child;

}


static PXMLNODE xml_node_get_child_by_name (PXMLNODE node, char *name, int index)
{
	PLISTITERATOR iter  = NULL;
	PXMLNODE      child = NULL;

	if (index < 0)
		return NULL;

	iter = node->childList->iterator (node->childList);
	while (iter->hasNext (iter))
	{
		child = iter->next (iter);
		if (strcmp (name, child->getName (child)) == 0)
		{
			if (index-- == 0)
			{
				iter->destroy (iter);
				return child;
			}
		}
	}
	iter->destroy (iter);
	return NULL;
}


static PXMLNODE xml_node_get_descent_by_name (PXMLNODE node, int count, ...)
{
	PXMLNODE child = node;
	va_list  list;
	va_start (list, count);
	while (count-- > 0)
	{
		char *name = va_arg (list, char *);
		child = child->getChildByName (child, name, 0);
		if (child == NULL)
		{
			va_end (list);
			return NULL;
		}
	}
	va_end (list);
	return child;
}


static char* xml_node_get_child_text_content_by_name (PXMLNODE node, char *name, int index)
{
	PXMLNODE child = NULL;

	if (node == NULL)
		return NULL;

	child = node->getChildByName (node, name, index);
	if (child == NULL)
		return node->getAttribute (node, name);

	return child->getTextContent (child);
}


static int xml_node_get_child_index (PXMLNODE node, PXMLNODE src)
{
	PLISTITERATOR iter  = NULL;
	int           index = 0;

	iter = node->childList->iterator (node->childList);
	while (iter->hasNext (iter))
	{
		PXMLNODE child = iter->next (iter);
		if (child == src)
		{
			iter->destroy (iter);
			return index;
		}
		index++;
	}
	iter->destroy (iter);
	return -1;
}

static bool xml_node_has_child (PXMLNODE node, PXMLNODE child)
{
	return node->childList->contains (node->childList, child);
}


static bool xml_node_bind (PXMLNODE node, PXMLBIND bind)
{
#define BIND_ADDRESS(type)	((type *) ((char *) bind->address))
#define BIND_DEREFER(type)	(* BIND_ADDRESS (type))
	bool success = true;
	for (; bind->name != NULL; bind++)
	{
		const char *value = node->getChildTextContentByName (node, bind->name, 0);
		if (value == NULL)
		{
			caution ("cannot find attribute(%s)", bind->name);
			success = false;
			continue;
		}

		switch (bind->type)
		{
		case BOOL:   BIND_DEREFER (bool)   = (STRING_EQUAL_IGNORECASE (value, "true") || STRING_EQUAL (value, "1"))? true: false; break;
		case CHAR:   BIND_DEREFER (char)   = string_to_char (value); break;
		case SHORT:  BIND_DEREFER (short)  = (short) strtol (value, NULL, 10); break;
		case INT:    BIND_DEREFER (int)    = strtol (value, NULL, 10); break;
		case LONG:   BIND_DEREFER (long)   = strtoll (value, NULL, 10); break;
		case FLOAT:  BIND_DEREFER (float)  = (float) strtod (value, NULL); break;
		case DOUBLE: BIND_DEREFER (double) = strtod (value, NULL); break;
		case STRING: strcpy (BIND_ADDRESS (char), value); break;
		default:
			caution ("cannot bind variable(%s) unknow type", bind->name);
			success = false;
			break;
		}
	}
	return success;
}

static bool xml_node_bind_static (PXMLNODE node, PXMLBIND_STATIC bind, void *object)
{
#define STATIC_BIND_ADDRESS(type)	((type *) (((char *) object) + bind->offset))
#define STATIC_BIND_DEREFER(type)	(* STATIC_BIND_ADDRESS (type))
	bool success = true;
	for (; bind->name != NULL; bind++)
	{
		const char *value = node->getChildTextContentByName (node, bind->name, 0);
		if (value == NULL)
		{
			caution ("cannot find attribute(%s)", bind->name);
			success = false;
			continue;
		}

		switch (bind->type)
		{
		case BOOL:   STATIC_BIND_DEREFER (bool)   = (STRING_EQUAL_IGNORECASE (value, "true") || STRING_EQUAL (value, "1"))? true: false; break;
		case CHAR:   STATIC_BIND_DEREFER (char)   = string_to_char (value); break;
		case SHORT:  STATIC_BIND_DEREFER (short)  = (short) strtol (value, NULL, 10); break;
		case INT:    STATIC_BIND_DEREFER (int)    = strtol (value, NULL, 10); break;
		case LONG:   STATIC_BIND_DEREFER (long)   = strtoll (value, NULL, 10); break;
		case FLOAT:  STATIC_BIND_DEREFER (float)  = (float) strtod (value, NULL); break;
		case DOUBLE: STATIC_BIND_DEREFER (double) = strtod (value, NULL); break;
		case STRING: strcpy (STATIC_BIND_ADDRESS (char), value); break;
		default:
			caution ("cannot bind variable(%s) unknow type", bind->name);
			success = false;
			break;
		}
	}
	return success;
}


PXMLNODE xml_node_create (PXMLNODE parent)
{
	PXMLNODE node = (PXMLNODE) malloc (sizeof (XMLNODE));

	//1. initialize memeber function
	node->destroy = xml_node_destroy;
	
	node->getName = xml_node_get_name;
	node->getTextContent = xml_node_get_text_content;
	node->getAttribute = xml_node_get_attribute;
	node->getChildById = xml_node_get_child_by_id;
	node->getChildByAttribute = xml_node_get_child_by_attribute;
	node->getChildByIndex = xml_node_get_child_by_index;
	node->getChildByName = xml_node_get_child_by_name;
	node->getDescentByName = xml_node_get_descent_by_name;
	node->getChildTextContentByName = xml_node_get_child_text_content_by_name;
	node->getChildrenByAttribute = xml_node_get_children_by_attribute;
	node->getChildIndex = xml_node_get_child_index;
	node->getParent = xml_node_get_parent;
	node->getAttributeCount = xml_node_get_attribute_count;
	node->getChildCount = xml_node_get_child_count;

	node->hasChild = xml_node_has_child;
	
	node->setName = xml_node_set_name;
	node->setTextContent = xml_node_set_text_content;
	node->appendAttribute = xml_node_append_attribute;
	node->appendChild = xml_node_append_child;
	
	node->removeAttribute = xml_node_remove_attribute;
	node->removeChild = xml_node_remove_child;
	node->removeChildByIndex = xml_node_remove_child_by_index;
	
	node->readXML = xml_node_read_xml;
	node->writeXML = xml_node_write_xml;
	node->writeXMLNoWS = xml_node_write_xml_no_ws;
	node->bind = xml_node_bind;
	node->bindStatic = xml_node_bind_static;

	//2. initialize member variable
	node->attributeMap = strstrmap_create ();
	node->childList = list_create ();
	node->textContent = NULL;
	node->name = NULL;
	
	//setting parent - child relation
	if (parent == NULL)
	{
		 node->parent = NULL;
	}
	else
	{
		node->parent = parent;
		parent->appendChild (parent, node);
	}

	return node;
}

PXMLNODE xml_node_copy (PXMLNODE nodeSrc)
{
	PXMLNODE      nodeDest = xml_node_create (NULL);
	PMAPITERATOR  iter = NULL;

	nodeDest->setName (nodeDest, nodeSrc->name);
	nodeDest->setTextContent (nodeDest, nodeSrc->textContent);

	iter = nodeSrc->attributeMap->iterator (nodeSrc->attributeMap);
	while (iter->hasNext (iter))
	{
		PAIR pair = iter->next (iter);
		nodeDest->appendAttribute (nodeDest, 
				(char *) pair.key,
				(char *) pair.value);
	}
	iter->destroy (iter);
	return nodeDest;
}
